#ifndef VECTOR4D_H
#define VECTOR4D_H

#include "Prerequisites.h"
#include "Vector3D.h"


/** 4-dimensional homogenous vector.
*/
class Vector4D
{
public:
	float x, y, z, w;

public:
	inline Vector4D()
		: x(0.0f), y(0.0f), z(0.0f), w(0.0f)
	{
	}

	inline Vector4D( const float fX, const float fY, const float fZ, const float fW )
		: x( fX ), y( fY ), z( fZ ), w( fW)
	{
	}

	inline Vector4D(const Vector3D& fV, const float fW)
		: x(fV.x), y(fV.y), z(fV.z), w(fW)
	{
	}

	inline explicit Vector4D( const float afCoordinate[4] )
		: x( afCoordinate[0] ),
		  y( afCoordinate[1] ),
		  z( afCoordinate[2] ),
		  w( afCoordinate[3] )
	{
	}

	inline explicit Vector4D( const int afCoordinate[4] )
	{
		x = (float)afCoordinate[0];
		y = (float)afCoordinate[1];
		z = (float)afCoordinate[2];
		w = (float)afCoordinate[3];
	}

	inline explicit Vector4D( float* const r )
		: x( r[0] ), y( r[1] ), z( r[2] ), w( r[3] )
	{
	}

	inline explicit Vector4D( const float scaler )
		: x( scaler )
		, y( scaler )
		, z( scaler )
		, w( scaler )
	{
	}

	inline explicit Vector4D(const Vector3D& rhs)
		: x(rhs.x), y(rhs.y), z(rhs.z), w(1.0f)
	{
	}

	inline Vector4D( const Vector4D& rkVector )
		: x( rkVector.x ), y( rkVector.y ), z( rkVector.z ), w (rkVector.w)
	{
	}

	inline float operator [] ( const size_t i ) const
	{
		assert( i < 4 );

		return *(&x+i);
	}

	inline float& operator [] ( const size_t i )
	{
		assert( i < 4 );

		return *(&x+i);
	}

	/// Pointer accessor for direct copying
	inline float* ptr()
	{
		return &x;
	}
	/// Pointer accessor for direct copying
	inline const float* ptr() const
	{
		return &x;
	}

	/** Assigns the value of the other vector.
		@param
			rkVector The other vector
	*/
	inline Vector4D& operator = ( const Vector4D& rkVector )
	{
		x = rkVector.x;
		y = rkVector.y;
		z = rkVector.z;
		w = rkVector.w;

		return *this;
	}

	inline Vector4D& operator = ( const float fScalar)
	{
		x = fScalar;
		y = fScalar;
		z = fScalar;
		w = fScalar;
		return *this;
	}

	inline bool operator == ( const Vector4D& rkVector ) const
	{
		return ( x == rkVector.x &&
			y == rkVector.y &&
			z == rkVector.z &&
			w == rkVector.w );
	}

	inline bool operator != ( const Vector4D& rkVector ) const
	{
		return ( x != rkVector.x ||
			y != rkVector.y ||
			z != rkVector.z ||
			w != rkVector.w );
	}

	inline Vector4D& operator = (const Vector3D& rhs)
	{
		x = rhs.x;
		y = rhs.y;
		z = rhs.z;
		w = 1.0f;
		return *this;
	}

	// arithmetic operations
	inline Vector4D operator + ( const Vector4D& rkVector ) const
	{
		return Vector4D(
			x + rkVector.x,
			y + rkVector.y,
			z + rkVector.z,
			w + rkVector.w);
	}

	inline Vector4D operator - ( const Vector4D& rkVector ) const
	{
		return Vector4D(
			x - rkVector.x,
			y - rkVector.y,
			z - rkVector.z,
			w - rkVector.w);
	}

	inline Vector4D operator * ( const float fScalar ) const
	{
		return Vector4D(
			x * fScalar,
			y * fScalar,
			z * fScalar,
			w * fScalar);
	}

	inline Vector4D operator * ( const Vector4D& rhs) const
	{
		return Vector4D(
			rhs.x * x,
			rhs.y * y,
			rhs.z * z,
			rhs.w * w);
	}

	inline Vector4D operator / ( const float fScalar ) const
	{
		assert( fScalar != 0.0 );

		float fInv = 1.0f / fScalar;

		return Vector4D(
			x * fInv,
			y * fInv,
			z * fInv,
			w * fInv);
	}

	inline Vector4D operator / ( const Vector4D& rhs) const
	{
		return Vector4D(
			x / rhs.x,
			y / rhs.y,
			z / rhs.z,
			w / rhs.w);
	}

	inline const Vector4D& operator + () const
	{
		return *this;
	}

	inline Vector4D operator - () const
	{
		return Vector4D(-x, -y, -z, -w);
	}

	inline friend Vector4D operator * ( const float fScalar, const Vector4D& rkVector )
	{
		return Vector4D(
			fScalar * rkVector.x,
			fScalar * rkVector.y,
			fScalar * rkVector.z,
			fScalar * rkVector.w);
	}

	inline friend Vector4D operator / ( const float fScalar, const Vector4D& rkVector )
	{
		return Vector4D(
			fScalar / rkVector.x,
			fScalar / rkVector.y,
			fScalar / rkVector.z,
			fScalar / rkVector.w);
	}

	inline friend Vector4D operator + (const Vector4D& lhs, const float rhs)
	{
		return Vector4D(
			lhs.x + rhs,
			lhs.y + rhs,
			lhs.z + rhs,
			lhs.w + rhs);
	}

	inline friend Vector4D operator + (const float lhs, const Vector4D& rhs)
	{
		return Vector4D(
			lhs + rhs.x,
			lhs + rhs.y,
			lhs + rhs.z,
			lhs + rhs.w);
	}

	inline friend Vector4D operator - (const Vector4D& lhs, float rhs)
	{
		return Vector4D(
			lhs.x - rhs,
			lhs.y - rhs,
			lhs.z - rhs,
			lhs.w - rhs);
	}

	inline friend Vector4D operator - (const float lhs, const Vector4D& rhs)
	{
		return Vector4D(
			lhs - rhs.x,
			lhs - rhs.y,
			lhs - rhs.z,
			lhs - rhs.w);
	}

	// arithmetic updates
	inline Vector4D& operator += ( const Vector4D& rkVector )
	{
		x += rkVector.x;
		y += rkVector.y;
		z += rkVector.z;
		w += rkVector.w;

		return *this;
	}

	inline Vector4D& operator -= ( const Vector4D& rkVector )
	{
		x -= rkVector.x;
		y -= rkVector.y;
		z -= rkVector.z;
		w -= rkVector.w;

		return *this;
	}

	inline Vector4D& operator *= ( const float fScalar )
	{
		x *= fScalar;
		y *= fScalar;
		z *= fScalar;
		w *= fScalar;
		return *this;
	}

	inline Vector4D& operator += ( const float fScalar )
	{
		x += fScalar;
		y += fScalar;
		z += fScalar;
		w += fScalar;
		return *this;
	}

	inline Vector4D& operator -= ( const float fScalar )
	{
		x -= fScalar;
		y -= fScalar;
		z -= fScalar;
		w -= fScalar;
		return *this;
	}

	inline Vector4D& operator *= ( const Vector4D& rkVector )
	{
		x *= rkVector.x;
		y *= rkVector.y;
		z *= rkVector.z;
		w *= rkVector.w;

		return *this;
	}

	inline Vector4D& operator /= ( const float fScalar )
	{
		assert( fScalar != 0.0 );

		float fInv = 1.0f / fScalar;

		x *= fInv;
		y *= fInv;
		z *= fInv;
		w *= fInv;

		return *this;
	}

	inline Vector4D& operator /= ( const Vector4D& rkVector )
	{
		x /= rkVector.x;
		y /= rkVector.y;
		z /= rkVector.z;
		w /= rkVector.w;

		return *this;
	}

	/** Calculates the dot (scalar) product of this vector with another.
		@param
			vec Vector with which to calculate the dot product (together
			with this one).
		@returns
			A float representing the dot product value.
	*/
	inline float dotProduct(const Vector4D& vec) const
	{
		return x * vec.x + y * vec.y + z * vec.z + w * vec.w;
	}
	/** Function for writing to a stream.
	*/
	inline friend std::ostream& operator <<
		( std::ostream& o, const Vector4D& v )
	{
		o << "Vector4D(" << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ")";
		return o;
	}
	// special
	static const Vector4D ZERO;

};

#endif
